/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.advice;

import cn.hutool.json.JSONUtil;
import com.taipingframework.boot.web.constant.AspectOrderEnum;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.extend.AspectHelper;
import com.taipingframework.utility.extend.ReactorHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Objects;
import java.util.StringJoiner;

/**
 * 日志拦截切面
 * 拦截通过注解标注的类和方法
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class HttpAspect implements Ordered {

    private final ApplicationContext applicationContext;

    /**
     * 设置切面的拦截范围 @Service
     */
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void servicePointcut() {
    }

    /**
     * 设置切面的拦截范围 @Controller
     */
    @Pointcut("@within(org.springframework.stereotype.Controller)")
    public void ctrlPointcut() {
    }

    /**
     * 设置切面的拦截范围 @RestController
     */
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void restPointcut() {
    }

    /**
     * 设置被切面拦截的注解 @RequestMapping
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void reqMapPointcut() {
    }

    /**
     * 设置被切面拦截的注解 @GetMapping
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getPointcut() {
    }

    /**
     * 设置被切面拦截的注解 @PostMapping
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postPointcut() {
    }

    /**
     * 设置被切面拦截的注解 @DeleteMapping
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void deletePointcut() {
    }

    /**
     * 设置被切面拦截的注解 @PutMapping
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
    public void putPointcut() {
    }

    /**
     * 设置切面被拦截的条件
     */
    @Pointcut("servicePointcut() && (reqMapPointcut() || getPointcut() || postPointcut() || deletePointcut() || putPointcut())")
    public void serviceRequestPointcut() {
    }

    /**
     * 设置切面被拦截的条件
     */
    @Pointcut("(ctrlPointcut() || restPointcut()) && (reqMapPointcut() || getPointcut() || postPointcut() || deletePointcut() || putPointcut())")
    public void httpRequestPointcut() {
    }

    /**
     * 实现切面（环绕通知）的执行逻辑
     */
    @Around("serviceRequestPointcut()")
    public Object serviceRequestAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        return this.requestAdvice(joinPoint);
    }

    /**
     * 实现切面（环绕通知）的执行逻辑
     */
    @Around("httpRequestPointcut()")
    public Object ctrlRequestAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        return this.requestAdvice(joinPoint);
    }

    private Object requestAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        if (applicationContext instanceof ServletWebServerApplicationContext) {
            // Servlet服务切面日志
            return adviceForServlet(joinPoint);
        } else if (applicationContext instanceof ReactiveWebServerApplicationContext) {
            // Reactive响应式服务切面日志
            return adviceForReactor(joinPoint);
        } else {
            // 非Web服务，不创建日志消息
            Object[] args = joinPoint.getArgs(); // 代理的方法参数列表
            return joinPoint.proceed(args);
        }
    }

    /**
     * spring-mvc 网络接口切面通知
     */
    private Object adviceForServlet(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        long startTime = System.currentTimeMillis();
        Object source = joinPoint.getTarget(); // 被代理的类实例对象
        String packagePathName = source.getClass().getTypeName(); // 被代理的类完整限定名，包括泛型
        String methodName = joinPoint.getSignature().getName(); // 被代理的方法名称

        try {
            // 前置通知
            // something will be done.
            Object[] args = joinPoint.getArgs(); // 代理的方法参数列表
            // Servlet服务切面日志
            StringJoiner joiner = AspectHelper.requestParamJoiner(packagePathName, methodName, args);
            log.info(joiner.toString());

            result = joinPoint.proceed(args);

            // 后置通知
            // something will be done.
            if (joiner.length() > 0) {
                log.debug("请求结束");
                String jsonResultValue = result == null ? null : JSONUtil.toJsonStr(result);
                log.info("返回结果response={}", jsonResultValue);
            }
        } catch (Throwable throwable) {
            // 异常通知
            // something will be done.
            log.error("执行 {}.{} 时发生异常", packagePathName, methodName, throwable);
            throw throwable;
        } finally {
            // 返回通知
            // something will be done.
            log.info("执行 {}.{} 共耗时 {} 毫秒", packagePathName, methodName, System.currentTimeMillis() - startTime);
        }

        return result;
    }

    /**
     * spring-webflux网络接口切面通知
     */
    @SuppressWarnings("unchecked")
    private Object adviceForReactor(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        Object[] args = joinPoint.getArgs();
        Object result = joinPoint.proceed(args);

        Object source = joinPoint.getTarget(); // 被代理的类实例对象
        String packagePathName = source.getClass().getTypeName(); // 被代理的类完整限定名，包括泛型
        String methodName = joinPoint.getSignature().getName(); // 被代理的方法名称

        if (result instanceof Mono) {
            String[] requestIdHolder = new String[1];
            return ReactorHelper.getContext()
                    .map(ctx -> {
                        requestIdHolder[0] = "[" + ctx.get(ApplicationConstant.RESPONSE_IDENTITY) + "]";
                        ServerWebExchange exchange = ctx.get(ServerWebExchange.class);
                        ServerHttpRequest request = exchange.getRequest();
                        StringJoiner joiner = AspectHelper.requestParamJoiner(request, packagePathName, methodName, args);
                        log.info(requestIdHolder[0] + joiner.toString());
                        return ctx;
                    })
                    .log()
                    .then((Mono) result)
                    .doOnSuccess(obj -> {
                        // 后置通知
                        String responseContent = StringUtils.EMPTY;
                        if (Objects.nonNull(obj)) {
                            responseContent = String.valueOf(obj);
                        }
                        log.info(requestIdHolder[0] + "请求结束，返回结果response={}", responseContent);
                    })
                    .doOnError(Throwable.class, throwable -> {
                        // 异常通知
                        log.error(requestIdHolder[0] + "执行 {}.{} 时发生异常", packagePathName, methodName, throwable);
                    })
                    .doOnTerminate(() -> {
                        // 返回通知
                        log.info(requestIdHolder[0] + "执行 {}.{} 共耗时 {} 毫秒", packagePathName, methodName, System.currentTimeMillis() - startTime);
                    });

//            String[] requestIdHolder = new String[1];
//            Mono monoResult = (Mono) result;
//            return monoResult
//                    .log()
//                    .flatMap(ret -> Mono.subscriberContext()
//                            .map(ctx -> {
//                                // 前置通知
//                                try {
//                                    requestIdHolder[0] = "[" + ctx.get(ApplicationConstant.RESPONSE_IDENTITY) + "]";
//                                    ServerWebExchange exchange = ctx.get(ServerWebExchange.class);
//                                    ServerHttpRequest request = exchange.getRequest();
//                                    StringJoiner joiner = ReactiveAspectHelper.requestParamJoiner(request, packagePathName, methodName, args);
//                                    log.info(requestIdHolder[0] + joiner.toString());
//                                } catch (Exception e) {
//                                    log.error(requestIdHolder[0] + e.getMessage(), e);
//                                }
//
//                                return ret;
//                            }))
//                    .doOnSuccess(obj -> {
//                        // 后置通知
//                        String responseContent = StringUtils.EMPTY;
//                        if (Objects.nonNull(obj)) {
//                            responseContent = String.valueOf(obj);
//                        }
//                        log.info(requestIdHolder[0] + "请求结束，返回结果response={}", responseContent);
//                    })
//                    .doOnError(Throwable.class, throwable -> {
//                        // 异常通知
//                        log.error(requestIdHolder[0] + "执行 {}.{} 时发生异常", packagePathName, methodName, throwable);
//                    })
//                    .doFinally(type -> {
//                        // 返回通知
//                        log.info(requestIdHolder[0] + "执行 {}.{} 共耗时 {} 毫秒", packagePathName, methodName, System.currentTimeMillis() - startTime);
//                    });
        }

        return result;
    }

    /**
     * 设置切面逻辑的执行顺序
     */
    @Override
    public int getOrder() {
        return AspectOrderEnum.REQUEST_ADVICE.getOrdered();
    }

    /*
     * 自定义订阅者
     *
     * 实际上，继承BaseSubscriber之后重新定义一个订阅者是比较推荐的一种做法。
     * 目前，唯一的难点是此处对于方法currentContext()的实现，且尚未探索到一种可行的方案。
     * 此处的代码仅提供一种实现思路！
     *
     * @param <T> 被订阅序列的引用类型
     *
    static class SampleSubscriber<T> extends BaseSubscriber<T> {
        Consumer<T> consumer;
        long startTimestamp;
        String packagePathName, methodName;

        SampleSubscriber(ProceedingJoinPoint joinPoint, long startTimestamp, Consumer<T> consumer) {
            this.consumer = consumer;
            this.startTimestamp = startTimestamp;
            // 解析切面数据
            Object[] args = joinPoint.getArgs();
            Object source = joinPoint.getTarget(); // 被代理的类实例对象
            this.packagePathName = source.getClass().getTypeName(); // 被代理的类完整限定名，包括泛型
            this.methodName = joinPoint.getSignature().getName(); // 被代理的方法名称

            // 前置通知
            ServerWebExchange exchange = this.currentContext().get(ServerWebExchange.class);
            ServerHttpRequest request = exchange.getRequest();
            StringJoiner joiner = ReactiveAspectHelper.requestParamJoiner(request, packagePathName, methodName, args);
            log.info(joiner.toString());
        }

        @NonNull
        @Override
        public Context currentContext() {
            return Context.empty();
        }

        @Override
        protected void hookOnSubscribe(Subscription subscription) {
            request(1);
        }

        @Override
        protected void hookOnNext(T value) {
            // 后置通知
            consumer.accept(value);
            request(1);
        }

        @Override
        protected void hookOnError(Throwable throwable) {
            // 异常通知
            // something will be done.
            log.error("执行 {}.{} 时发生异常", packagePathName, methodName, throwable);
        }

        @Override
        protected void hookOnComplete() {
            // 返回通知
            // something will be done.
            log.info("执行 {}.{} 共耗时 {} 毫秒", packagePathName, methodName, System.currentTimeMillis() - startTimestamp);
        }
    }*/
}
